package se.lnu.axez.tools;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

import se.lnu.axez.Constants;
import se.lnu.axez.TriMesh;

/**
 * Generates axiZ {@link TriMesh} successor from mesh file formats.
 * 
 * @author stan
 * 
 */
public final class TriMeshGenerator {
	private static final int ASSIGNMENTS_LIMIT = 2000;
	private static final String FILL_VERTEX_ARRAY_FUNCTION_NAME = "v";
	private static final String FILL_NORMALS_ARRAY_FUNCTION_NAME = "n";
	private static final String FILL_UV_ARRAY_FUNCTION_NAME = "t";
	
	private TriMeshGenerator() {}
	
	public static void main(String[] files) throws IOException {
		ObjLoader objLoader = new ObjLoader();
		
		for (String filePath : files) {
			File file = new File(filePath);
			if (!file.exists()) {
				System.out.println("Cannot find file " + file.getAbsolutePath());
				continue;
			}
			
			String className = getFilenameWithoutExtension(file);

			// capitalize
			className = className.substring(0, 1).toUpperCase() + className.substring(1) + "TriMesh";
			
			InputStream is = new BufferedInputStream(new FileInputStream(file));
			try {
				objLoader.loadArrays(is);
			} finally {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			File outputFile = new File(file.getParent(), className + ".java");
			composeClass(outputFile, className, objLoader);
		}
	}

	private static void composeClass(File outputFile, String className, ObjLoader objLoader) throws IOException {
		Writer w = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(outputFile)));
		try {
			final String newLine = System.getProperty("line.separator");
			
			// generate imports
			w.write("import se.lnu.axez.TriMesh;");
			w.write(newLine);
			w.write(newLine);
			
			// generate class comment
			w.write("/**");
			w.write(newLine);
			w.write(" * ");
			w.write("This class is generated by ");
			w.write(TriMeshGenerator.class.getName());
			w.write(" tool.");
			w.write(newLine);
			w.write(" */");
			w.write(newLine);
			
			// generate class declaration
			w.write("public final class ");
			w.write(className);
			w.write(" extends TriMesh {");
			w.write(newLine);
			w.write(newLine);
			
			// generate getVertices method
			w.write("\t@Override");
			w.write(newLine);
			w.write("\tprotected float[] getVertices() {");
			w.write(newLine);
			float[] facesArray = objLoader.getFaceVertices();
			w.write("\t\tfinal float[] a = new float[" + facesArray.length + "];");
			w.write(newLine);
			w.write("\t\t");
			w.write(FILL_VERTEX_ARRAY_FUNCTION_NAME);
			w.write("(a);");
			w.write(newLine);
			w.write("\t\treturn a;");
			w.write(newLine);
			w.write("\t}");
			w.write(newLine);
			w.write(newLine);
			
			// generate getNormals method
			w.write("\t@Override");
			w.write(newLine);
			w.write("\tprotected float[] getNormals() {");
			w.write(newLine);
			float[] normalsArray = objLoader.getFaceNormals();
			w.write("\t\tfinal float[] a = new float[" + normalsArray.length + "];");
			w.write(newLine);
			w.write("\t\t");
			w.write(FILL_NORMALS_ARRAY_FUNCTION_NAME);
			w.write("(a);");
			w.write(newLine);
			w.write("\t\treturn a;");
			w.write(newLine);
			w.write("\t}");
			w.write(newLine);
			w.write(newLine);

//			// generate getUV method
			w.write("\t@Override");
			w.write(newLine);
			w.write("\tprotected float[] getUV() {");
			w.write(newLine);
			float[] uvArray = objLoader.getFaceNormals();
			w.write("\t\tfinal float[] a = new float[" + uvArray.length + "];");
			w.write(newLine);
			w.write("\t\t");
			w.write(FILL_UV_ARRAY_FUNCTION_NAME);
			w.write("(a);");
			w.write(newLine);
			w.write("\t\treturn a;");
			w.write(newLine);
			w.write("\t}");
			w.write(newLine);
			w.write(newLine);
			
			// generate getIndexCount method
			w.write("\t@Override");
			w.write(newLine);
			w.write("\tprotected int getIndexCount() {");
			w.write(newLine);
			w.write("\t\treturn ");
			w.write(Integer.toString(objLoader.getFacesCount() * Constants.VERTEX_COORDINATES));
			w.write(";");
			w.write(newLine);
			w.write("\t}");
			w.write(newLine);
			w.write(newLine);
			
			writeArrayItems(w, FILL_VERTEX_ARRAY_FUNCTION_NAME, facesArray);
			writeArrayItems(w, FILL_NORMALS_ARRAY_FUNCTION_NAME, normalsArray);
			writeArrayItems(w, FILL_UV_ARRAY_FUNCTION_NAME, uvArray);
			
			// generate class ending
			w.write("}");
			w.write(newLine);
		} finally {
			w.close();
		}
	}

	private static void writeArrayItems(Writer w, String rootFunctionName, float[] array) throws IOException {
		// 2000 float values per subfunction
		
		final String newLine = System.getProperty("line.separator");

		int functionCounter = 0;
		int assignmentsCounter = 0;
		for (int i = 0; i < array.length; i++) {
			if (assignmentsCounter == 0) {
				w.write("\tprivate static void " + rootFunctionName + functionCounter + "(final float[] a) {");
				w.write(newLine);
				functionCounter++;
			}

			w.write("\t\ta[" + i + "] = ");
			w.write(Float.toString(array[i]));
			w.write("f;");
			w.write(newLine);
			
			assignmentsCounter++;

			if (assignmentsCounter == ASSIGNMENTS_LIMIT) {
				assignmentsCounter = 0;
				w.write("\t}");
				w.write(newLine);
				w.write(newLine);
			}
		}

		if (assignmentsCounter != 0) {
			w.write("\t}");
			w.write(newLine);
			w.write(newLine);
		}

		w.write("\tprivate static void " + rootFunctionName + "(final float[] a) {");
		w.write(newLine);
		
		for (int i = 0; i < functionCounter; i++) {
			w.write("\t\t");
			w.write(rootFunctionName + i);
			w.write("(a);");
			w.write(newLine);
		}
		
		w.write("\t}");
		w.write(newLine);
		w.write(newLine);
	}

	private static String getFilenameWithoutExtension(File file) {
		int index = file.getName().lastIndexOf('.');
		if (index > 0 && index <= file.getName().length() - 2) {
			return file.getName().substring(0, index);
		}

		return "";
	}
}
